package com.kaihong.template.opinterface;

import ohos.ace.ability.AceInternalAbility;
import ohos.agp.utils.TextTool;
import ohos.app.AbilityContext;
import ohos.rpc.IRemoteObject;
import ohos.rpc.MessageOption;
import ohos.rpc.MessageParcel;
import ohos.rpc.RemoteException;
import ohos.utils.zson.ZSONObject;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import com.kaihong.template.ble.BasicConstants;
import com.kaihong.template.ble.operation.BleOperator;
import com.kaihong.template.ble.operation.callback.adapter.BluetoothAdapterStateChangeCallback;
import com.kaihong.template.ble.operation.callback.adapter.BluetoothDeviceFoundCallback;
import com.kaihong.template.ble.operation.callback.gatt.BleCharacteristicValueChangeCallback;
import com.kaihong.template.ble.operation.callback.gatt.BleConnectionStateChangeCallback;
import com.kaihong.template.ble.operation.callback.gatt.BleServicesDiscoveredCallback;
import com.kaihong.template.ble.operation.callback.gatt.EnableNotifyIndicateCallback;
import com.kaihong.template.ble.operation.callback.gatt.ReadBleCharacteristicValueCallback;
import com.kaihong.template.ble.operation.callback.gatt.ReadBleDescriptorValueCallback;
import com.kaihong.template.ble.operation.callback.gatt.WriteBleCharacteristicValueCallback;
import com.kaihong.template.ble.operation.callback.gatt.WriteBleDescriptorValueCallback;
import com.kaihong.template.ble.operation.impl.BleOperatorImpl;
import com.kaihong.template.ble.operation.model.BluetoothDiscoveryParam;
import com.kaihong.template.ble.operation.model.DescriptorData;
import com.kaihong.template.utils.Constants;
import com.kaihong.template.utils.Logger;
import com.kaihong.template.utils.TextUtils;

/**
 * Ble Operator Ability
 */
public class BleOperatorAbility extends AceInternalAbility {
    private static final String TAG = "BleOperatorAbility";

    private static final String TAG_ID = "tagId";//根据tagId取消订阅

    private static final String DEVICE_ID = "deviceId";

    private static final String SERVICE_ID = "serviceId";

    private static final String CHARACTERISTIC_ID = "characteristicId";

    private static final String CHANGE_DATA = "changeData";

    private static final String READ_DATA = "readData";

    private static final String IS_CONNECTED = "isConnected";

    private static final String DEVICES = "devices";

    private static final String ADVERTISE_SERVICE_UUID_S = "advertiseServiceUuids";

    private static final String LOCAL_NAME = "localName";

    private static final String SERVICE_DATA = "serviceData";

    private static final String IS_DISCOVERING = "isDiscovering";

    private static final String IS_AVAILABLE = "isAvailable";

    private static final String CODE = "code";

    private static final String MESSAGE = "message";

    private static final String DATA = "data";

    private static final String WRITE_DATA = "writeData";

    private static final String DESCRIPTOR_ID = "descriptorId";

    private static final int BLE_STATE_CHANGE_REQUEST_CODE = 100;

    private static BleOperatorAbility sInstance;

    private AbilityContext context;

    private BleOperator bleOperator;

    private final Set<IRemoteObject> bleDeviceFoundCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> bleAdapterStateChangeCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> bleConnectionStateChangeCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> bleServicesDiscoveredCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> readBleCharacteristicValueCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> writeBleCharacteristicValueCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> bleCharacteristicValueChangeCallbackSet = new HashSet<>();

    private final Map<String, IRemoteObject> bleCharacteristicValueChangeCallbackMap = new HashMap<>();

    private final Set<IRemoteObject> enableNotifyIndicateCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> readBleDescriptorValueCallbackSet = new HashSet<>();

    private final Set<IRemoteObject> writeBleDescriptorValueCallbackSet = new HashSet<>();

    private BleOperatorAbility() {
        super(Constants.TEMPLATE_BUNDLE_NAME, BleOperatorAbility.class.getName());
    }

    /**
     * register Internal Ability
     *
     * @param abilityContext abilityContext
     */
    public static void register(AbilityContext abilityContext) {
        if (abilityContext == null) {
            Logger.i(TAG, "register abilityContext is null");
        } else {
            Logger.i(TAG, "register " + abilityContext.getBundleName());
            sInstance = new BleOperatorAbility();
            sInstance.onRegister(abilityContext);
        }
    }

    private void onRegister(AbilityContext abilityContext) {
        Logger.i(TAG, "onRegister");
        context = abilityContext;
        bleOperator = BleOperatorImpl.getInstance(context);
        setInternalAbilityHandler(this::onRemoteRequest);
    }

    private boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
        Logger.i(TAG, "onRemoteRequest code:" + code);
        switch (code) {
            case OperatorCode.OPEN_BLUETOOTH_ADAPTER:
                return openBluetoothAdapterCase(data, reply, option);
            case OperatorCode.CLOSE_BLUETOOTH_ADAPTER:
                return closeBluetoothAdapterCase(data, reply, option);
            case OperatorCode.GET_BLUETOOTH_ADAPTER_STATE:
                return getBluetoothAdapterStateCase(data, reply, option);
            case OperatorCode.ON_BLUETOOTH_ADAPTER_STATE_CHANGE:
                return onBluetoothAdapterStateChangeCase(data, reply, option);
            case OperatorCode.START_BLUETOOTH_DEVICES_DISCOVERY:
                return startBluetoothDevicesDiscoveryCase(data, reply, option);
            case OperatorCode.STOP_BLUETOOTH_DEVICES_DISCOVERY:
                return stopBluetoothDevicesDiscoveryCase(data, reply, option);
            case OperatorCode.ON_BLUETOOTH_DEVICE_FOUND:
                return onBluetoothDeviceFoundCase(data, reply, option);
            case OperatorCode.CREATE_BLE_CONNECTION:
                return createBleConnectionCase(data, reply, option);
            case OperatorCode.CLOSE_BLE_CONNECTION:
                return closeBleConnectionCase(data, reply, option);
            case OperatorCode.ON_BLE_CONNECTION_STATE_CHANGE:
                return onBleConnectionStateChangeCase(data, reply, option);
            case OperatorCode.GET_DEVICE_ID:
                return getDeviceIdCase(reply, option);
            default:
                return onRemoteRequestPart(code, data, reply, option);
        }
    }

    private boolean onRemoteRequestPart(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
        switch (code) {
            case OperatorCode.ON_BLE_SERVICES_DISCOVERED:
                return onBleServicesDiscoveredCase(data, reply, option);
            case OperatorCode.READ_BLE_CHARACTERISTIC_VALUE:
                return readBleCharacteristicValueCase(data, reply, option);
            case OperatorCode.WRITE_BLE_CHARACTERISTIC_VALUE:
                return writeBleCharacteristicValueCase(data, reply, option);
            case OperatorCode.ON_BLE_CHARACTERISTIC_VALUE_CHANGE:
                return onBleCharacteristicChangeCase(data, reply, option);
            case OperatorCode.NOTIFY_BLE_CHARACTERISTIC_VALUE_CHANGE:
                return notifyBleCharacteristicValueChangeCase(data, reply, option);
            case OperatorCode.SET_ENABLE_INDICATION:
                return setEnableIndicationCase(data, reply, option);
            case OperatorCode.ON_ENABLE_NOTIFY_INDICATE:
                return onEnableNotifyIndicateCase(data, reply, option);
            case OperatorCode.READ_BLE_DESCRIPTOR_VALUE:
                return readBleDescriptorValueCase(data, reply, option);
            case OperatorCode.WRITE_BLE_DESCRIPTOR_VALUE:
                return writeBleDescriptorValueCase(data, reply, option);
            case OperatorCode.GET_BLE_CONNECTION_STATE:
                return getBleConnectionStateCase(data, reply, option);
            default:
                onRemoteRequestDefaultCase(reply);
                return false;
        }
    }

    private boolean openBluetoothAdapterCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        String errorCode = bleOperator.openBluetoothAdapter();
        return replyResult(reply, option, parseErrorCode(errorCode), new HashMap<>());
    }

    private boolean closeBluetoothAdapterCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        String errorCode = bleOperator.closeBluetoothAdapter();
        return replyResult(reply, option, parseErrorCode(errorCode), new HashMap<>());
    }

    private boolean getBluetoothAdapterStateCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        boolean isAvailable = bleOperator.getBluetoothAdapterState();
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put(IS_AVAILABLE, isAvailable);
        return replyResult(reply, option, BasicConstants.SUCCESS, resultMap);
    }

    private boolean onBluetoothAdapterStateChangeCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        bleAdapterStateChangeCallbackSet.clear();
        bleAdapterStateChangeCallbackSet.add(data.readRemoteObject());
        onBluetoothAdapterStateChange();
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean startBluetoothDevicesDiscoveryCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        BluetoothDiscoveryParam param = ZSONObject.stringToClass(dataString, BluetoothDiscoveryParam.class);
        String errorCode = bleOperator.startBluetoothDevicesDiscovery(getUuidArrayByStringArray(param.getServices()),
                param.getMacAdress(), param.isAllowDuplicatesKey(), param.getInterval());
        int code = 0;
        try {
            code = Integer.parseInt(errorCode);
        } catch (NumberFormatException e) {
            Logger.e(TAG, "NumberFormatException");
        }
        return replyResult(reply, option, code, new HashMap<>());
    }

    private boolean stopBluetoothDevicesDiscoveryCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        String errorCode = bleOperator.stopBluetoothDevicesDiscovery();
        return replyResult(reply, option, parseErrorCode(errorCode), new HashMap<>());
    }

    private boolean onBluetoothDeviceFoundCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        bleDeviceFoundCallbackSet.clear();
        bleDeviceFoundCallbackSet.add(data.readRemoteObject());
        onBluetoothDeviceFound();
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean createBleConnectionCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
        if (TextUtils.isEmpty(deviceId)) {
            Logger.e(TAG, "deviceId can not be null");
            return true;
        }
        String errorCode = bleOperator.createBleConnection(deviceId);
        return replyResult(reply, option, parseErrorCode(errorCode), new HashMap<>());
    }


    private boolean closeBleConnectionCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
//        if (TextUtils.isEmpty(deviceId)) {
//            Logger.e(TAG, "deviceId can not be null");
//            return true;
//        }
        String errorCode = bleOperator.closeBleConnection(deviceId);
        return replyResult(reply, option, parseErrorCode(errorCode), new HashMap<>());
    }

    private boolean onBleConnectionStateChangeCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        bleConnectionStateChangeCallbackSet.clear();
        bleConnectionStateChangeCallbackSet.add(data.readRemoteObject());
        Logger.i(TAG, "bleConnectionStateChangeCallbackSet size : " + bleConnectionStateChangeCallbackSet.size());
        onBleConnectionStateChange();
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean getDeviceIdCase(MessageParcel reply, MessageOption option) {
        String deviceId = bleOperator.getDeviceId();
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put(DEVICE_ID, deviceId);
        return replyResult(reply, option, BasicConstants.SUCCESS, resultMap);
    }

    private boolean onBleServicesDiscoveredCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        bleServicesDiscoveredCallbackSet.clear();
        bleServicesDiscoveredCallbackSet.add(data.readRemoteObject());
        onBleServicesDiscovered();
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean readBleCharacteristicValueCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        readBleCharacteristicValueCallbackSet.clear();
        readBleCharacteristicValueCallbackSet.add(data.readRemoteObject());
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
        String serviceId = String.valueOf(dataObject.get(SERVICE_ID));
        String characteristicId = String.valueOf(dataObject.get(CHARACTERISTIC_ID));
        readBleCharacteristicValue(deviceId, serviceId, characteristicId);
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean writeBleCharacteristicValueCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        writeBleCharacteristicValueCallbackSet.clear();
        writeBleCharacteristicValueCallbackSet.add(data.readRemoteObject());
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
        String serviceId = String.valueOf(dataObject.get(SERVICE_ID));
        String characteristicId = String.valueOf(dataObject.get(CHARACTERISTIC_ID));
        String writeData = String.valueOf(dataObject.get(WRITE_DATA));
        writeBleCharacteristicValue(deviceId, serviceId, characteristicId, writeData);
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean onBleCharacteristicChangeCase(MessageParcel data, MessageParcel reply, MessageOption option) {
//        bleCharacteristicValueChangeCallbackSet.clear();
        bleCharacteristicValueChangeCallbackSet.add(data.readRemoteObject());
        onBleCharacteristicValueChange();
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    /**
//     * 实现可根据tagId取消订阅
//     *
//     * @param data TAG_ID
//     */
//    private boolean onBleCharacteristicChangeCase2(MessageParcel data, MessageParcel reply, MessageOption option) {
//        String dataString = data.readString();
//        Logger.i(TAG, "dataString:" + dataString);
//        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
//        String tagId = String.valueOf(dataObject.get(TAG_ID));
//        bleCharacteristicValueChangeCallbackMap.put(tagId, data.readRemoteObject());
//        onBleCharacteristicValueChange();
//        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
//    }
//
//    /**
//     * 取消订阅
//     * @param data TAG_ID
//     */
//    private boolean unBleCharacteristicChangeCase(MessageParcel data, MessageParcel reply, MessageOption option) {
//        String dataString = data.readString();
//        Logger.i(TAG, "dataString:" + dataString);
//        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
//        String tagId = String.valueOf(dataObject.get(TAG_ID));
//
//        bleCharacteristicValueChangeCallbackMap.remove(tagId);
//        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
//    }

    private boolean notifyBleCharacteristicValueChangeCase(MessageParcel data, MessageParcel reply,
                                                           MessageOption option) {
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
        String serviceId = String.valueOf(dataObject.get(SERVICE_ID));
        String characteristicId = String.valueOf(dataObject.get(CHARACTERISTIC_ID));
        boolean isState = dataObject.getBooleanValue("state");
        Logger.i(TAG, "start to call operator");
        String errorCode =
                bleOperator.notifyBleCharacteristicValueChange(deviceId, serviceId, characteristicId, isState);
        return replyResult(reply, option, parseErrorCode(errorCode), new HashMap<>());
    }

    private boolean setEnableIndicationCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        Object isEnableIndicationObj = dataObject.get("isEnableIndication");
        if (isEnableIndicationObj instanceof Boolean) {
            bleOperator.setEnableIndication((boolean) isEnableIndicationObj);
        } else {
            Logger.e(TAG, "isEnableIndication must be a boolean value");
            return false;
        }
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean onEnableNotifyIndicateCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        enableNotifyIndicateCallbackSet.clear();
        enableNotifyIndicateCallbackSet.add(data.readRemoteObject());
        onEnableNotifyIndicate();
        return replyResult(reply, option, BasicConstants.SUCCESS, new HashMap<>());
    }

    private boolean readBleDescriptorValueCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        readBleDescriptorValueCallbackSet.clear();
        readBleDescriptorValueCallbackSet.add(data.readRemoteObject());
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
        String serviceId = String.valueOf(dataObject.get(SERVICE_ID));
        String characteristicId = String.valueOf(dataObject.get(CHARACTERISTIC_ID));
        String descriptorIdId = String.valueOf(dataObject.get(DESCRIPTOR_ID));
        String errorCode = readBleDescriptorValue(deviceId, serviceId, characteristicId, descriptorIdId);
        return replyResult(reply, option, parseErrorCode(errorCode), new HashMap<>());
    }

    private boolean writeBleDescriptorValueCase(MessageParcel data, MessageParcel reply, MessageOption option) {
        writeBleDescriptorValueCallbackSet.clear();
        writeBleDescriptorValueCallbackSet.add(data.readRemoteObject());
        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
        String serviceId = String.valueOf(dataObject.get(SERVICE_ID));
        String characteristicId = String.valueOf(dataObject.get(CHARACTERISTIC_ID));
        String descriptorId = String.valueOf(dataObject.get(DESCRIPTOR_ID));
        String writeData = String.valueOf(dataObject.get(WRITE_DATA));
        String errorCode = writeBleDescriptorValue(deviceId, serviceId, characteristicId, descriptorId, writeData);
        int code = 0;
        try {
            code = Integer.parseInt(errorCode);
        } catch (NumberFormatException e) {
            Logger.e(TAG, "NumberFormatException");
        }
        return replyResult(reply, option, code, new HashMap<>());
    }

    private boolean getBleConnectionStateCase(MessageParcel data, MessageParcel reply, MessageOption option) {

        String dataString = data.readString();
        Logger.i(TAG, "dataString:" + dataString);
        ZSONObject dataObject = ZSONObject.stringToZSON(dataString);
        String deviceId = String.valueOf(dataObject.get(DEVICE_ID));
        boolean isConnected = bleOperator.getBleConnectionState(deviceId);
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put(DEVICE_ID, deviceId);
        resultMap.put(IS_CONNECTED, isConnected);
        return replyResult(reply, option, BasicConstants.SUCCESS, resultMap);
    }

    private int parseErrorCode(String errorCode) {
        int code = OperatorCode.ERROR_CODE_COMMON_ERR;
        try {
            code = Integer.parseInt(errorCode);
        } catch (NumberFormatException e) {
            Logger.e(TAG, "NumberFormatException errorCode:" + errorCode);
        }
        return code;
    }

    private void onRemoteRequestDefaultCase(MessageParcel reply) {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put(MESSAGE, BasicConstants.ERROR_MESSAGE_NO_SERVICE);
        resultMap.put(CODE, BasicConstants.REQUEST_CODE_ERROR);
        reply.writeString(ZSONObject.toZSONString(resultMap));
    }

    private boolean replyResult(MessageParcel reply, MessageOption option, int code, Map<String, Object> resultMap) {
        if (option.getFlags() == MessageOption.TF_SYNC) {
            // SYNC
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put(CODE, code);
            dataMap.put(DATA, resultMap);
            reply.writeString(ZSONObject.toZSONString(dataMap));
        } else {
            // ASYNC
            MessageParcel responseData = MessageParcel.obtain();
            responseData.writeString(ZSONObject.toZSONString(resultMap));
            IRemoteObject remoteReply = reply.readRemoteObject();
            try {
                remoteReply.sendRequest(code, responseData, MessageParcel.obtain(), new MessageOption());
                responseData.reclaim();
            } catch (RemoteException exception) {
                Logger.e(TAG, "RemoteException", exception);
                return false;
            }
        }
        return true;
    }

    private void sendRequest(IRemoteObject remoteObject, int errorCode, MessageParcel data, MessageParcel reply,
                             MessageOption option) {
        if (remoteObject == null) {
            return;
        }
        try {
            remoteObject.sendRequest(errorCode, data, reply, option);
        } catch (RemoteException e) {
            Logger.e(TAG, "RemoteException:", e);
        }
    }

    private void onBluetoothAdapterStateChange() {
        BluetoothAdapterStateChangeCallback callback =
                (isDiscovering, isAvailable) -> {
                    Logger.i(TAG, "onBluetoothAdapterStateChange isDiscovering:" + isDiscovering);
                    Logger.i(TAG, "onBluetoothAdapterStateChange isAvailable:" + isAvailable);
                    MessageParcel data = MessageParcel.obtain();
                    MessageParcel reply = MessageParcel.obtain();
                    MessageOption option = new MessageOption();
                    Map<String, Object> resultMap = new HashMap<>();
                    resultMap.put(IS_DISCOVERING, isDiscovering);
                    resultMap.put(IS_AVAILABLE, isAvailable);
                    data.writeString(ZSONObject.toZSONString(resultMap));
                    for (IRemoteObject remoteObject : bleAdapterStateChangeCallbackSet) {
                        sendRequest(remoteObject, 100, data, reply, option);
                    }
                    reply.reclaim();
                    data.reclaim();
                };
        bleOperator.onBluetoothAdapterStateChange(callback);
    }

    private UUID[] getUuidArrayByStringArray(String[] strings) {
        if (strings == null || strings.length == 0) {
            return new UUID[0];
        }
        UUID[] uuids = new UUID[strings.length];
        for (int i = 0; i < strings.length; i++) {
            uuids[i] = UUID.fromString(strings[i]);
        }
        return uuids;
    }

    private void onBluetoothDeviceFound() {
        BluetoothDeviceFoundCallback callback =
                (devices, deviceId, advertiseServiceUuids, localName, serviceData) -> {
                    Logger.i(
                            TAG,
                            "onBluetoothDeviceFound devices:"
                                    + Arrays.toString(devices)
                                    + ", deviceId:"
                                    + deviceId
                                    + ", advertiseServiceUuids:"
                                    + Arrays.toString(advertiseServiceUuids)
                                    + ", "
                                    + "localName:"
                                    + localName
                                    + ", serviceData:"
                                    + serviceData);
                    Map<String, Object> resultMap = new HashMap<>();
                    resultMap.put(DEVICES, devices);
                    resultMap.put(DEVICE_ID, deviceId);
                    resultMap.put(ADVERTISE_SERVICE_UUID_S, advertiseServiceUuids);
                    resultMap.put(LOCAL_NAME, localName);
                    resultMap.put(SERVICE_DATA, serviceData);
                    MessageParcel data = MessageParcel.obtain();
                    data.writeString(ZSONObject.toZSONString(resultMap));
                    MessageParcel reply = MessageParcel.obtain();
                    MessageOption option = new MessageOption();
                    for (IRemoteObject remoteObject : bleDeviceFoundCallbackSet) {
                        sendRequest(remoteObject, BasicConstants.SUCCESS, data, reply, option);
                    }
                    reply.reclaim();
                    data.reclaim();
                };
        bleOperator.onBluetoothDeviceFound(callback);
    }

    private void onBleConnectionStateChange() {
        BleConnectionStateChangeCallback callback = (deviceId, isConnected) -> {
            Logger.i(TAG, "onBLEConnectionStateChange deviceId:" + deviceId + ", isConnected:" + isConnected);
            MessageParcel data = MessageParcel.obtain();
            MessageParcel reply = MessageParcel.obtain();
            MessageOption option = new MessageOption();
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put(DEVICE_ID, deviceId);
            resultMap.put(IS_CONNECTED, isConnected);
            data.writeString(ZSONObject.toZSONString(resultMap));
            for (IRemoteObject remoteObject : bleConnectionStateChangeCallbackSet) {
                sendRequest(remoteObject, BasicConstants.SUCCESS, data, reply, option);
            }
            reply.reclaim();
            data.reclaim();
        };
        bleOperator.onBleConnectionStateChange(callback);
    }

    private void onBleServicesDiscovered() {
        BleServicesDiscoveredCallback callback =
                errorCode -> {
                    Logger.i(TAG, "onBleServicesDiscovered errorCode:" + errorCode);
                    MessageParcel data = MessageParcel.obtain();
                    MessageParcel reply = MessageParcel.obtain();
                    MessageOption option = new MessageOption();
                    data.writeString(ZSONObject.toZSONString(new HashMap<>()));
                    Logger.i(TAG, "bleServicesDiscoveredCallbackSet size = " + bleServicesDiscoveredCallbackSet.size());
                    for (IRemoteObject remoteObject : bleServicesDiscoveredCallbackSet) {
                        sendRequest(remoteObject, errorCode, data, reply, option);
                    }
                    reply.reclaim();
                    data.reclaim();
                };
        bleOperator.onBleServicesDiscovered(callback);
    }

    private void readBleCharacteristicValue(String deviceId, String serviceId, String characteristicId) {
        ReadBleCharacteristicValueCallback callback = (readData, errorCode) -> {
            Logger.i(TAG, "readBleCharacteristicValue readData:" + readData);
            Logger.i(TAG, "readBleCharacteristicValue errorCode:" + errorCode);
            MessageParcel data = MessageParcel.obtain();
            MessageParcel reply = MessageParcel.obtain();
            MessageOption option = new MessageOption();
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put(READ_DATA, readData);
            data.writeString(ZSONObject.toZSONString(resultMap));
            for (IRemoteObject remoteObject : readBleCharacteristicValueCallbackSet) {
                sendRequest(remoteObject, errorCode, data, reply, option);
            }
            reply.reclaim();
            data.reclaim();
        };
        bleOperator.readBleCharacteristicValue(deviceId, serviceId, characteristicId, callback);
    }

    private void writeBleCharacteristicValue(
            String deviceId, String serviceId, String characteristicId, String writeData) {
        WriteBleCharacteristicValueCallback callback =
                (write, errorCode) -> {
                    Logger.i(TAG, "writeBleCharacteristicValue writeData:" + write);
                    Logger.i(TAG, "writeBleCharacteristicValue errorCode:" + errorCode);
                    MessageParcel data = MessageParcel.obtain();
                    MessageParcel reply = MessageParcel.obtain();
                    MessageOption option = new MessageOption();
                    Map<String, Object> resultMap = new HashMap<>();
                    resultMap.put(WRITE_DATA, write);
                    data.writeString(ZSONObject.toZSONString(resultMap));
                    for (IRemoteObject remoteObject : writeBleCharacteristicValueCallbackSet) {
                        sendRequest(remoteObject, errorCode, data, reply, option);
                    }
                    reply.reclaim();
                    data.reclaim();
                };
        DescriptorData descriptorData = new DescriptorData(deviceId, serviceId, characteristicId, null, writeData);
        bleOperator.writeBleCharacteristicValue(descriptorData, callback);
    }

    private void onBleCharacteristicValueChange() {
        BleCharacteristicValueChangeCallback callback = (deviceId, characteristicId, changeData) -> {
            Logger.i(TAG, "onBLECharacteristicValueChange deviceId: " + deviceId + ", characteristicId: "
                    + characteristicId + ", changeData: " + changeData);
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put(DEVICE_ID, deviceId);
            resultMap.put(CHARACTERISTIC_ID, characteristicId);
            resultMap.put(CHANGE_DATA, changeData);
            MessageParcel reply = MessageParcel.obtain();
            MessageParcel data = MessageParcel.obtain();
            data.writeString(ZSONObject.toZSONString(resultMap));
            MessageOption option = new MessageOption();
            for (IRemoteObject remoteObject : bleCharacteristicValueChangeCallbackSet) {
                sendRequest(remoteObject, BasicConstants.SUCCESS, data, reply, option);
            }
            reply.reclaim();
            data.reclaim();
        };
        bleOperator.onBleCharacteristicValueChange(callback);
    }

    private void onEnableNotifyIndicate() {
        EnableNotifyIndicateCallback callback = errorCode -> {
            Logger.i(TAG, "onEnableNotifyIndicate errorCode:" + errorCode);
            MessageParcel data = MessageParcel.obtain();
            MessageParcel reply = MessageParcel.obtain();
            MessageOption option = new MessageOption();
            data.writeString(ZSONObject.toZSONString(new HashMap<>()));
            for (IRemoteObject remoteObject : enableNotifyIndicateCallbackSet) {
                sendRequest(remoteObject, errorCode, data, reply, option);
            }
            reply.reclaim();
            data.reclaim();
        };
        bleOperator.onEnableNotifyIndicate(callback);
    }

    private String readBleDescriptorValue(
            String deviceId, String serviceId, String characteristicId, String descriptorIdId) {
        ReadBleDescriptorValueCallback callback = (readData, errorCode) -> {
            Logger.i(TAG, "readBleDescriptorValue readData:" + readData);
            Logger.i(TAG, "readBleDescriptorValue errorCode:" + errorCode);
            MessageParcel data = MessageParcel.obtain();
            MessageParcel reply = MessageParcel.obtain();
            MessageOption option = new MessageOption();
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put(READ_DATA, readData);
            data.writeString(ZSONObject.toZSONString(resultMap));
            for (IRemoteObject remoteObject : readBleDescriptorValueCallbackSet) {
                sendRequest(remoteObject, errorCode, data, reply, option);
            }
            reply.reclaim();
            data.reclaim();
        };
        return bleOperator.readBleDescriptorValue(deviceId, serviceId, characteristicId, descriptorIdId, callback);
    }

    private String writeBleDescriptorValue(
            String deviceId, String serviceId, String characteristicId, String descriptorIdId, String writeData) {
        WriteBleDescriptorValueCallback callback = errorCode -> {
            Logger.i(TAG, "writeBleDescriptorValue errorCode:" + errorCode);
            MessageParcel data = MessageParcel.obtain();
            MessageParcel reply = MessageParcel.obtain();
            MessageOption option = new MessageOption();
            data.writeString(ZSONObject.toZSONString(new HashMap<>()));
            for (IRemoteObject remoteObject : writeBleDescriptorValueCallbackSet) {
                sendRequest(remoteObject, errorCode, data, reply, option);
            }
            reply.reclaim();
            data.reclaim();
        };
        DescriptorData descriptorData =
                new DescriptorData(deviceId, serviceId, characteristicId, descriptorIdId, writeData);
        return bleOperator.writeBleDescriptorValue(
                descriptorData, callback);
    }

    /**
     * unregister handler
     */
    public static void unregister() {
        if (sInstance != null) {
            sInstance.onUnregister();
        }
    }

    private void onUnregister() {
        context = null;
        setInternalAbilityHandler(null);
    }

    public static BleOperatorAbility getInstance() {
        return sInstance;
    }
}
